﻿namespace DomainServices.Tools
{
    using Microsoft.CSharp;
    using Microsoft.VisualBasic;
    using System;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.ServiceModel.DomainServices;
    using System.ServiceModel.DomainServices.Server;
    using System.Text;

    internal abstract class CodeDomClientCodeGenerator : IDomainServiceClientCodeGenerator, ILogger
    {
        private ClientCodeGenerationOptions _clientProxyCodeGenerationOptions;
        private CodeCompileUnit _compileUnit;
        private List<DomainServiceDescription> _domainServiceDescriptions;
        private HashSet<Type> _enumTypesToGenerate;
        private ICodeGenerationHost _host;
        private Dictionary<string, CodeNamespace> _namespaces;
        private CodeGeneratorOptions _options;
        private CodeDomProvider _provider;
        private static string[] fixedImports = new string[] { "System", "System.Collections.Generic", "System.ComponentModel", "System.ComponentModel.DataAnnotations", "System.Linq", "System.ServiceModel.DomainServices", "System.ServiceModel.DomainServices.Client", "System.ServiceModel.DomainServices.Client.ApplicationServices" };
        internal const string GeneratorName = "DomainServices.Tools.CodeDomClientCodeGenerator";

        internal bool CanExposeEnumType(Type enumType, out string errorMessage)
        {
            errorMessage = null;
            if (!enumType.IsPublic || enumType.IsNested)
            {
                errorMessage = DomainServices.Tools.Resource.Enum_Type_Must_Be_Public;
                return false;
            }
            if (((this.GetTypeShareKind(enumType) & CodeMemberShareKind.Shared) == CodeMemberShareKind.Unknown) && enumType.Assembly.IsSystemAssembly())
            {
                errorMessage = DomainServices.Tools.Resource.Enum_Type_Cannot_Gen_System;
                return false;
            }
            return true;
        }

        private void Cleanup()
        {
            CodeDomProvider provider = this._provider;
            this._provider = null;
            if (provider != null)
            {
                provider.Dispose();
            }
            this._compileUnit = null;
            this._namespaces = null;
            this._enumTypesToGenerate = null;
            this._domainServiceDescriptions = null;
            this._host = null;
            this._options = null;
            this._clientProxyCodeGenerationOptions = null;
        }

        private void EnsureInitialized()
        {
            if (this._clientProxyCodeGenerationOptions == null)
            {
                throw new InvalidOperationException(DomainServices.Tools.Resource.ClientProxyGenerator_Initialize_Not_Called);
            }
        }

        private void FixUpCompileUnit(CodeCompileUnit compileUnit)
        {
            new ClientProxyFixupCodeDomVisitor(this.ClientProxyCodeGenerationOptions).Visit(compileUnit);
        }

        private string FixupVBOptionStatements(string code)
        {
            if (this.IsCSharp || (code == null))
            {
                return code;
            }
            StringBuilder builder = new StringBuilder(code);
            string str = "Option Strict Off";
            string newValue = "Option Strict On";
            string str3 = "Option Infer On";
            string str4 = "Option Compare Binary";
            int index = code.IndexOf(str, StringComparison.Ordinal);
            if (index != -1)
            {
                builder.Replace(str, newValue, index, str.Length);
                builder.Insert(index, str3 + Environment.NewLine);
                builder.Insert(index, str4 + Environment.NewLine);
            }
            return builder.ToString();
        }

        internal void GenerateAllEnumTypes()
        {
            foreach (Type type in this._enumTypesToGenerate)
            {
                CodeNamespace orGenNamespace = this.GetOrGenNamespace(type.Namespace);
                CodeTypeDeclaration declaration = CodeGenUtilities.CreateEnumTypeDeclaration(type, this);
                orGenNamespace.Types.Add(declaration);
            }
        }

        public string GenerateCode(ICodeGenerationHost host, IEnumerable<DomainServiceDescription> descriptions, ClientCodeGenerationOptions options)
        {
            string str;
            try
            {
                this.Initialize(host, descriptions, options);
                str = this.GenerateProxyClass();
            }
            finally
            {
                this.Cleanup();
            }
            return str;
        }

        private void GenerateDataContractTypes(IEnumerable<Type> typesToGenerate, List<Type> generatedTypes, string sharedError, Action<Type> generateType)
        {
            foreach (Type type in from e in typesToGenerate
                orderby e.Name
                select e)
            {
                if (!generatedTypes.Contains(type))
                {
                    if (this.GetTypeShareKind(type) == CodeMemberShareKind.SharedByReference)
                    {
                        this.LogError(string.Format(CultureInfo.CurrentCulture, sharedError, new object[] { type }));
                    }
                    else
                    {
                        generatedTypes.Add(type);
                        generateType(type);
                    }
                }
            }
        }

        private string GenerateProxyClass()
        {
            Action<Type> generateType = null;
            string str = string.Empty;
            ICollection<DomainServiceDescription> allDescriptions = this._domainServiceDescriptions;
            List<Type> generatedTypes = new List<Type>();
            List<Type> list2 = new List<Type>();
            Dictionary<Type, CodeTypeDeclaration> typeMapping = new Dictionary<Type, CodeTypeDeclaration>();
            Queue<CodeProcessorWorkItem> queue = new Queue<CodeProcessorWorkItem>();
            this.PreprocessProxyTypes();
            using (IEnumerator<DomainServiceDescription> enumerator = (from d in allDescriptions
                orderby d.DomainServiceType.Name
                select d).GetEnumerator())
            {
                Action<Type> action = null;
                System.Func<KeyValuePair<Type, CodeTypeDeclaration>, bool> func = null;
                DomainServiceDescription dsd;
                while (enumerator.MoveNext())
                {
                    dsd = enumerator.Current;
                    if ((this.GetDomainContextTypeMemberShareKind(dsd) & CodeMemberShareKind.Shared) != CodeMemberShareKind.Unknown)
                    {
                        this.LogMessage(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.Shared_DomainContext_Skipped, new object[] { dsd.DomainServiceType.Name }));
                    }
                    else
                    {
                        this.LogMessage(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_Generating_DomainService, new object[] { dsd.DomainServiceType.FullName }));
                        new DomainServiceProxyGenerator(this, dsd, typeMapping).Generate();
                        if (generateType == null)
                        {
                            generateType = delegate (Type t) {
                                new EntityProxyGenerator(this, t, allDescriptions, typeMapping).Generate();
                            };
                        }
                        this.GenerateDataContractTypes(dsd.EntityTypes, generatedTypes, DomainServices.Tools.Resource.ClientCodeGen_EntityTypesCannotBeShared_Reference, generateType);
                        if (action == null)
                        {
                            action = delegate (Type t) {
                                new ComplexTypeProxyGenerator(this, t, dsd, typeMapping).Generate();
                            };
                        }
                        this.GenerateDataContractTypes(dsd.ComplexTypes, list2, DomainServices.Tools.Resource.ClientCodeGen_ComplexTypesCannotBeShared_Reference, action);
                        DomainIdentifierAttribute attribute = dsd.Attributes.OfType<DomainIdentifierAttribute>().SingleOrDefault<DomainIdentifierAttribute>();
                        if (attribute != null)
                        {
                            Type codeProcessor = attribute.CodeProcessor;
                            if (codeProcessor != null)
                            {
                                if (func == null)
                                {
                                    func = delegate (KeyValuePair<Type, CodeTypeDeclaration> kvp) {
                                        if (kvp.Key != dsd.DomainServiceType)
                                        {
                                            return dsd.EntityTypes.Contains<Type>(kvp.Key);
                                        }
                                        return true;
                                    };
                                }
                                Dictionary<Type, CodeTypeDeclaration> dictionary = Enumerable.ToDictionary<KeyValuePair<Type, CodeTypeDeclaration>, Type, CodeTypeDeclaration>(Enumerable.Where<KeyValuePair<Type, CodeTypeDeclaration>>(typeMapping, func), kvp => kvp.Key, kvp => kvp.Value);
                                queue.Enqueue(new CodeProcessorWorkItem(codeProcessor, dsd, dictionary));
                            }
                        }
                    }
                }
            }
            if (this.ClientProxyCodeGenerationOptions.IsApplicationContextGenerationEnabled)
            {
                new WebContextGenerator(this).Generate();
            }
            else if (allDescriptions.Count == 0)
            {
                return str;
            }
            this.GenerateAllEnumTypes();
            this.FixUpCompileUnit(this.CompileUnit);
            while (queue.Count > 0)
            {
                CodeProcessorWorkItem workItem = queue.Dequeue();
                this.InvokeDomainServiceCodeProcessor(workItem);
            }
            if (this.CodeGenerationHost.HasLoggedErrors)
            {
                return str;
            }
            using (TextWriter writer = new StringWriter(CultureInfo.InvariantCulture))
            {
                this.Provider.GenerateCodeFromCompileUnit(this.CompileUnit, writer, this._options);
                return this.FixupVBOptionStatements(writer.ToString());
            }
        }

        internal CodeMemberShareKind GetDomainContextTypeMemberShareKind(DomainServiceDescription domainServiceDescription)
        {
            Type domainServiceType = domainServiceDescription.DomainServiceType;
            string str = DomainServiceProxyGenerator.DomainContextTypeName(domainServiceDescription);
            string typeName = domainServiceType.Namespace + "." + str;
            return this.CodeGenerationHost.GetTypeShareKind(typeName);
        }

        internal CodeMemberShareKind GetMethodShareKind(MethodBase methodBase)
        {
            IEnumerable<string> parameterTypeNames = from p in methodBase.GetParameters() select p.ParameterType.AssemblyQualifiedName;
            return this.CodeGenerationHost.GetMethodShareKind(methodBase.DeclaringType.AssemblyQualifiedName, methodBase.Name, parameterTypeNames);
        }

        internal CodeNamespace GetNamespace(CodeTypeDeclaration typeDecl)
        {
            CodeNamespace namespace2;
            this.EnsureInitialized();
            string key = typeDecl.UserData["Namespace"] as string;
            if (key == null)
            {
                key = string.Empty;
            }
            this._namespaces.TryGetValue(key, out namespace2);
            return namespace2;
        }

        internal CodeNamespace GetOrGenNamespace(string namespaceName)
        {
            this.EnsureInitialized();
            CodeNamespace namespace2 = null;
            string key = namespaceName;
            if (key == null)
            {
                key = string.Empty;
            }
            if (!this._namespaces.TryGetValue(key, out namespace2))
            {
                namespace2 = new CodeNamespace(namespaceName);
                this._namespaces[key] = namespace2;
                if (!this.ClientProxyCodeGenerationOptions.UseFullTypeNames)
                {
                    foreach (string str2 in fixedImports)
                    {
                        CodeNamespaceImport import = new CodeNamespaceImport(str2);
                        namespace2.Imports.Add(import);
                    }
                }
                this.CompileUnit.Namespaces.Add(namespace2);
            }
            return namespace2;
        }

        internal CodeNamespace GetOrGenNamespace(Type type)
        {
            this.EnsureInitialized();
            return this.GetOrGenNamespace(type.Namespace);
        }

        internal CodeMemberShareKind GetPropertyShareKind(Type type, string propertyName)
        {
            return this.CodeGenerationHost.GetPropertyShareKind(type.AssemblyQualifiedName, propertyName);
        }

        internal CodeMemberShareKind GetTypeShareKind(Type type)
        {
            return this.CodeGenerationHost.GetTypeShareKind(type.AssemblyQualifiedName);
        }

        internal void Initialize(ICodeGenerationHost host, IEnumerable<DomainServiceDescription> descriptions, ClientCodeGenerationOptions options)
        {
            if (host == null)
            {
                throw new ArgumentNullException("host");
            }
            if (options == null)
            {
                throw new ArgumentNullException("options");
            }
            if (descriptions == null)
            {
                throw new ArgumentNullException("descriptions");
            }
            this._host = host;
            this._clientProxyCodeGenerationOptions = options;
            this._domainServiceDescriptions = descriptions.ToList<DomainServiceDescription>();
            this._compileUnit = new CodeCompileUnit();
            this._namespaces = new Dictionary<string, CodeNamespace>();
            this._enumTypesToGenerate = new HashSet<Type>();
            ValidateOptions(this._clientProxyCodeGenerationOptions);
            CodeGeneratorOptions options2 = new CodeGeneratorOptions {
                IndentString = "    ",
                VerbatimOrder = false,
                BlankLinesBetweenMembers = true,
                BracingStyle = "C"
            };
            this._options = options2;
            string language = this.ClientProxyCodeGenerationOptions.Language;
            bool flag = string.IsNullOrEmpty(language) || string.Equals(language, "C#", StringComparison.OrdinalIgnoreCase);
            this._provider = flag ? ((CodeDomProvider) new CSharpCodeProvider()) : ((CodeDomProvider) new VBCodeProvider());
            CodeGenUtilities.Initialize(!this.IsCSharp, this.ClientProxyCodeGenerationOptions.UseFullTypeNames, this.ClientProxyCodeGenerationOptions.ClientRootNamespace);
        }

        private void InvokeDomainServiceCodeProcessor(CodeProcessorWorkItem workItem)
        {
            Type codeProcessorType = workItem.CodeProcessorType;
            DomainServiceDescription domainServiceDescription = workItem.DomainServiceDescription;
            IDictionary<Type, CodeTypeDeclaration> typeMapping = workItem.TypeMapping;
            if (!typeof(CodeProcessor).IsAssignableFrom(codeProcessorType))
            {
                this.LogError(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_DomainService_CodeProcessor_NotValidType, new object[] { codeProcessorType, domainServiceDescription.DomainServiceType }));
            }
            else
            {
                ConstructorInfo info = codeProcessorType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(CodeDomProvider) }, null);
                if (info == null)
                {
                    this.LogError(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_DomainService_CodeProcessor_InvalidConstructorSignature, new object[] { codeProcessorType, domainServiceDescription.DomainServiceType, typeof(CodeDomProvider) }));
                }
                else
                {
                    try
                    {
                        (info.Invoke(new object[] { this._provider }) as CodeProcessor).ProcessGeneratedCode(domainServiceDescription, this.CompileUnit, typeMapping);
                    }
                    catch (Exception innerException)
                    {
                        if (innerException.IsFatal())
                        {
                            throw;
                        }
                        TargetInvocationException exception2 = innerException as TargetInvocationException;
                        if ((exception2 != null) && (exception2.InnerException != null))
                        {
                            innerException = exception2.InnerException;
                        }
                        this.LogError(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_DomainService_CodeProcessor_ExceptionCaught, new object[] { codeProcessorType, domainServiceDescription.DomainServiceType, innerException.Message }));
                        throw;
                    }
                }
            }
        }

        public void LogError(string message)
        {
            this.CodeGenerationHost.LogError(message);
        }

        public void LogMessage(string message)
        {
            this.CodeGenerationHost.LogMessage(message);
        }

        public void LogWarning(string message)
        {
            this.CodeGenerationHost.LogWarning(message);
        }

        internal bool NeedToGenerateEnumType(Type enumType)
        {
            if (!this._enumTypesToGenerate.Contains(enumType) && ((this.GetTypeShareKind(enumType) & CodeMemberShareKind.Shared) != CodeMemberShareKind.Unknown))
            {
                return false;
            }
            return true;
        }

        private void PreprocessProxyTypes()
        {
            foreach (DomainServiceDescription description in this.DomainServiceDescriptions)
            {
                if (description.DomainServiceType.IsNested)
                {
                    this.LogError(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_DomainService_CannotBeNested, new object[] { description.DomainServiceType }));
                }
                this.RegisterTypeName(description.DomainServiceType, description.DomainServiceType.Namespace);
                foreach (Type type in description.EntityTypes)
                {
                    this.RegisterTypeName(type, description.DomainServiceType.Namespace);
                }
            }
        }

        private void RegisterTypeName(Type type, string containingNamespace)
        {
            System.Func<Type, bool> func = null;
            System.Func<Type, bool> func2 = null;
            if (string.IsNullOrEmpty(type.Namespace))
            {
                this.LogError(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Namespace_Required, new object[] { type }));
            }
            else if (!CodeGenUtilities.RegisterTypeName(type, containingNamespace))
            {
                if (func == null)
                {
                    func = entity => entity.Namespace == type.Namespace;
                }
                if (func2 == null)
                {
                    func2 = dst => dst.Namespace == type.Namespace;
                }
                //foreach (Type type2 in Enumerable.Where<Type>(from d in this.DomainServiceDescriptions select d.EntityTypes, func).Concat<Type>(Enumerable.Where<Type>(from d in this.DomainServiceDescriptions select d.DomainServiceType, func2)).Distinct<Type>())
                //{
                //    CodeGenUtilities.RegisterTypeName(type2, containingNamespace);
                //}
            }
        }

        internal void RegisterUseOfEnumType(Type enumType)
        {
            if (!this._enumTypesToGenerate.Contains(enumType) && this.NeedToGenerateEnumType(enumType))
            {
                this._enumTypesToGenerate.Add(enumType);
            }
        }

        private static void ValidateOptions(ClientCodeGenerationOptions clientProxyCodeGenerationOptions)
        {
            if (clientProxyCodeGenerationOptions == null)
            {
                throw new ArgumentNullException("clientProxyCodeGenerationOptions");
            }
            if (string.IsNullOrEmpty(clientProxyCodeGenerationOptions.Language))
            {
                throw new ArgumentException(DomainServices.Tools.Resource.Null_Language_Property, "clientProxyCodeGenerationOptions");
            }
        }

        internal string ClientProjectName
        {
            get
            {
                return Path.GetFileNameWithoutExtension(this.ClientProxyCodeGenerationOptions.ClientProjectPath);
            }
        }

        internal ClientCodeGenerationOptions ClientProxyCodeGenerationOptions
        {
            get
            {
                return this._clientProxyCodeGenerationOptions;
            }
        }

        internal ICodeGenerationHost CodeGenerationHost
        {
            get
            {
                return this._host;
            }
        }

        private CodeCompileUnit CompileUnit
        {
            get
            {
                return this._compileUnit;
            }
        }

        internal IEnumerable<DomainServiceDescription> DomainServiceDescriptions
        {
            get
            {
                return this._domainServiceDescriptions.ToArray();
            }
        }

        public bool HasLoggedErrors
        {
            get
            {
                return this.CodeGenerationHost.HasLoggedErrors;
            }
        }

        internal bool IsCSharp
        {
            get
            {
                return (this.Provider is CSharpCodeProvider);
            }
        }

        private CodeDomProvider Provider
        {
            get
            {
                return this._provider;
            }
        }

        private class CodeProcessorWorkItem
        {
            public CodeProcessorWorkItem(Type codeProcessorType, System.ServiceModel.DomainServices.Server.DomainServiceDescription domainServiceDescription, Dictionary<Type, CodeTypeDeclaration> typeMapping)
            {
                this.CodeProcessorType = codeProcessorType;
                this.DomainServiceDescription = domainServiceDescription;
                this.TypeMapping = typeMapping;
            }

            public Type CodeProcessorType { get; private set; }

            public System.ServiceModel.DomainServices.Server.DomainServiceDescription DomainServiceDescription { get; private set; }

            public Dictionary<Type, CodeTypeDeclaration> TypeMapping { get; private set; }
        }
    }
}

